#define SourceDepth24

using System;
using AGG.UI;
using AGG.Transform;
using AGG.Image;
using AGG.VertexSource;
using AGG.RasterizerScanline;

namespace AGG
{
    public class blur_application : MatchParentBoundsWidget
    {
        RadioButtonWidget    m_method;
        SliderWidget  m_radius;
        PolygonEditWidget m_shadow_ctrl;
        CheckBoxWidget    m_channel_r;
        CheckBoxWidget    m_channel_g;
        CheckBoxWidget m_channel_b;
        CheckBoxWidget m_FlattenCurves;

        PathStorage m_path;
        FlattenCurves m_shape;
        
        ScanlineRasterizer m_ras = new ScanlineRasterizer();
        ScanlineCachePacked8 m_sl;
        ImageBuffer m_rbuf2;

        //agg::stack_blur    <agg::rgba8, agg::stack_blur_calc_rgb<> >     m_stack_blur;
        RecursiveBlur m_recursive_blur = new RecursiveBlur(new recursive_blur_calc_rgb());

        rect_d m_shape_bounds;

        public blur_application()
        {
            m_rbuf2 = new ImageBuffer(null);
            m_shape_bounds = new rect_d();
            m_method = new RadioButtonWidget(10.0, 10.0, 130.0, 70.0);
            m_radius = new SliderWidget(130 + 10.0, 10.0 + 4.0, 130 + 300.0, 10.0 + 8.0 + 4.0);
            m_shadow_ctrl = new PolygonEditWidget(4);
            m_channel_r = new CheckBoxWidget(10.0, 80.0, "Red");
            m_channel_g = new CheckBoxWidget(10.0, 95.0, "Green");
            m_channel_b = new CheckBoxWidget(10.0, 110.0, "Blue");
            m_FlattenCurves = new CheckBoxWidget(10, 315, "Convert And Flatten Curves");
            m_FlattenCurves.Checked = true;

            AddChild(m_method);
            m_method.text_size(8);
            m_method.add_item("Stack Blur");
            m_method.add_item("Recursive Blur");
            m_method.add_item("Channels");
            m_method.cur_item(1);

            AddChild(m_radius);
            m_radius.range(0.0, 40.0);
            m_radius.value(15.0);
            m_radius.label("Blur Radius={0:F2}");

            AddChild(m_shadow_ctrl);

            AddChild(m_channel_r);
            AddChild(m_channel_g);
            AddChild(m_channel_b);
            AddChild(m_FlattenCurves);
            m_channel_g.Checked = true;

            m_sl = new ScanlineCachePacked8();
            m_path = new PathStorage();
            m_shape = new FlattenCurves(m_path);

            m_path.remove_all();
            m_path.move_to(28.47, 6.45);
            m_path.curve3(21.58, 1.12, 19.82, 0.29);
            m_path.curve3(17.19, -0.93, 14.21, -0.93);
            m_path.curve3(9.57, -0.93, 6.57, 2.25);
            m_path.curve3(3.56, 5.42, 3.56, 10.60);
            m_path.curve3(3.56, 13.87, 5.03, 16.26);
            m_path.curve3(7.03, 19.58, 11.99, 22.51);
            m_path.curve3(16.94, 25.44, 28.47, 29.64);
            m_path.line_to(28.47, 31.40);
            m_path.curve3(28.47, 38.09, 26.34, 40.58);
            m_path.curve3(24.22, 43.07, 20.17, 43.07);
            m_path.curve3(17.09, 43.07, 15.28, 41.41);
            m_path.curve3(13.43, 39.75, 13.43, 37.60);
            m_path.line_to(13.53, 34.77);
            m_path.curve3(13.53, 32.52, 12.38, 31.30);
            m_path.curve3(11.23, 30.08, 9.38, 30.08);
            m_path.curve3(7.57, 30.08, 6.42, 31.35);
            m_path.curve3(5.27, 32.62, 5.27, 34.81);
            m_path.curve3(5.27, 39.01, 9.57, 42.53);
            m_path.curve3(13.87, 46.04, 21.63, 46.04);
            m_path.curve3(27.59, 46.04, 31.40, 44.04);
            m_path.curve3(34.28, 42.53, 35.64, 39.31);
            m_path.curve3(36.52, 37.21, 36.52, 30.71);
            m_path.line_to(36.52, 15.53);
            m_path.curve3(36.52, 9.13, 36.77, 7.69);
            m_path.curve3(37.01, 6.25, 37.57, 5.76);
            m_path.curve3(38.13, 5.27, 38.87, 5.27);
            m_path.curve3(39.65, 5.27, 40.23, 5.62);
            m_path.curve3(41.26, 6.25, 44.19, 9.18);
            m_path.line_to(44.19, 6.45);
            m_path.curve3(38.72, -0.88, 33.74, -0.88);
            m_path.curve3(31.35, -0.88, 29.93, 0.78);
            m_path.curve3(28.52, 2.44, 28.47, 6.45);
            m_path.ClosePolygon();

            m_path.move_to(28.47, 9.62);
            m_path.line_to(28.47, 26.66);
            m_path.curve3(21.09, 23.73, 18.95, 22.51);
            m_path.curve3(15.09, 20.36, 13.43, 18.02);
            m_path.curve3(11.77, 15.67, 11.77, 12.89);
            m_path.curve3(11.77, 9.38, 13.87, 7.06);
            m_path.curve3(15.97, 4.74, 18.70, 4.74);
            m_path.curve3(22.41, 4.74, 28.47, 9.62);
            m_path.ClosePolygon();

            Affine shape_mtx = Affine.NewIdentity();
            shape_mtx *= Affine.NewScaling(4.0);
            shape_mtx *= Affine.NewTranslation(150, 100);
            m_path.transform(shape_mtx);

            bounding_rect.bounding_rect_single(m_shape, 0, ref m_shape_bounds);

            m_shadow_ctrl.SetXN(0, m_shape_bounds.x1);
            m_shadow_ctrl.SetYN(0, m_shape_bounds.y1);
            m_shadow_ctrl.SetXN(1, m_shape_bounds.x2);
            m_shadow_ctrl.SetYN(1, m_shape_bounds.y1);
            m_shadow_ctrl.SetXN(2, m_shape_bounds.x2);
            m_shadow_ctrl.SetYN(2, m_shape_bounds.y2);
            m_shadow_ctrl.SetXN(3, m_shape_bounds.x1);
            m_shadow_ctrl.SetYN(3, m_shape_bounds.y2);
            m_shadow_ctrl.line_color(new RGBA_Doubles(0, 0.3, 0.5, 0.3));
        }

        bool m_MouseDown = false;
        public override void OnMouseDown(MouseEventArgs mouseEvent)
        {
            m_MouseDown = true;
            base.OnMouseDown(mouseEvent);
        }

        public override void OnMouseUp(MouseEventArgs mouseEvent)
        {
            m_MouseDown = false;
            base.OnMouseUp(mouseEvent);
        }

        public override void OnMouseMove(MouseEventArgs mouseEvent)
        {
            if (m_MouseDown)
            {
                Invalidate();
            }
            base.OnMouseMove(mouseEvent);
        }

        public override void OnDraw(Graphics2D graphics2D)
        {
            ImageClippingProxy clippingProxy = new ImageClippingProxy(graphics2D.DestImage);
            clippingProxy.clear(new RGBA_Doubles(1, 1, 1));
            m_ras.SetVectorClipBox(0, 0, Width, Height);

            Affine move = Affine.NewTranslation(10, 10);

            Perspective shadow_persp = new Perspective(m_shape_bounds.x1, m_shape_bounds.y1,
                                                m_shape_bounds.x2, m_shape_bounds.y2,
                                                m_shadow_ctrl.polygon());

            IVertexSource shadow_trans;
            if (m_FlattenCurves.Checked)
            {
                shadow_trans = new VertexSourceApplyTransform(m_shape, shadow_persp);
            }
            else
            {
                shadow_trans = new VertexSourceApplyTransform(m_path, shadow_persp);
                // this will make it very smooth after the transform
                //shadow_trans = new conv_curve(shadow_trans);
            }


            // Render shadow
            m_ras.add_path(shadow_trans);
            agg_renderer_scanline.Default.render_scanlines_aa_solid(clippingProxy, m_ras, m_sl, new RGBA_Doubles(0.2, 0.3, 0).GetAsRGBA_Bytes());

            // Calculate the bounding box and extend it by the blur radius
            rect_d bbox = new rect_d();
            bounding_rect.bounding_rect_single(shadow_trans, 0, ref bbox);

            bbox.x1 -= m_radius.value();
            bbox.y1 -= m_radius.value();
            bbox.x2 += m_radius.value();
            bbox.y2 += m_radius.value();

            if (m_method.cur_item() == 1)
            {
                // The recursive blur method represents the true Gaussian Blur,
                // with theoretically infinite kernel. The restricted window size
                // results in extra influence of edge pixels. It's impossible to
                // solve correctly, but extending the right and top areas to another
                // radius value produces fair result.
                //------------------
                bbox.x2 += m_radius.value();
                bbox.y2 += m_radius.value();
            }

            primaryWindow.start_timer();

            if(m_method.cur_item() != 2)
            {
                // Create a new pixel renderer and attach it to the main one as a child image. 
                // It returns true if the attachment succeeded. It fails if the rectangle 
                // (bbox) is fully clipped.
                //------------------
#if SourceDepth24
                ImageBuffer image2 = new ImageBuffer(new BlenderBGR());
#else
                ImageBuffer image2 = new ImageBuffer(new BlenderBGRA());
#endif
                if (image2.Attach(graphics2D.DestImage, (int)bbox.x1, (int)bbox.y1, (int)bbox.x2, (int)bbox.y2))
                {
                    // Blur it
                    if(m_method.cur_item() == 0)
                    {
                        // More general method, but 30-40% slower.
                        //------------------
                        //m_stack_blur.blur(pixf2, agg::uround(m_radius.value()));

                        // Faster, but bore specific. 
                        // Works only for 8 bits per channel and only with radii <= 254.
                        //------------------
                        stack_blur test = new stack_blur();
                        test.Blur(image2, agg_basics.uround(m_radius.value()), agg_basics.uround(m_radius.value()));
                    }
                    else
                    {
                        // True Gaussian Blur, 3-5 times slower than Stack Blur,
                        // but still constant time of radius. Very sensitive
                        // to precision, doubles are must here.
                        //------------------
                        m_recursive_blur.blur(image2, m_radius.value());
                    }
                }
            }
            else
            {
                /*
                // Blur separate channels
                //------------------
                if(m_channel_r.Checked)
                {
                    typedef agg::pixfmt_alpha_blend_gray<
                        agg::blender_gray8, 
                        agg::rendering_buffer,
                        3, 2> pixfmt_gray8r;

                    pixfmt_gray8r pixf2r(m_rbuf2);
                    if(pixf2r.attach(pixf, int(bbox.x1), int(bbox.y1), int(bbox.x2), int(bbox.y2)))
                    {
                        agg::stack_blur_gray8(pixf2r, agg::uround(m_radius.value()), 
                                                      agg::uround(m_radius.value()));
                    }
                }

                if(m_channel_g.Checked)
                {
                    typedef agg::pixfmt_alpha_blend_gray<
                        agg::blender_gray8, 
                        agg::rendering_buffer,
                        3, 1> pixfmt_gray8g;

                    pixfmt_gray8g pixf2g(m_rbuf2);
                    if(pixf2g.attach(pixf, int(bbox.x1), int(bbox.y1), int(bbox.x2), int(bbox.y2)))
                    {
                        agg::stack_blur_gray8(pixf2g, agg::uround(m_radius.value()), 
                                                      agg::uround(m_radius.value()));
                    }
                }

                if(m_channel_b.Checked)
                {
                    typedef agg::pixfmt_alpha_blend_gray<
                        agg::blender_gray8, 
                        agg::rendering_buffer,
                        3, 0> pixfmt_gray8b;

                    pixfmt_gray8b pixf2b(m_rbuf2);
                    if(pixf2b.attach(pixf, int(bbox.x1), int(bbox.y1), int(bbox.x2), int(bbox.y2)))
                    {
                        agg::stack_blur_gray8(pixf2b, agg::uround(m_radius.value()), 
                                                      agg::uround(m_radius.value()));
                    }
                }
                 */
            }

            double tm = primaryWindow.elapsed_time();

            // Render the shape itself
            //------------------
            if (m_FlattenCurves.Checked)
            {
                m_ras.add_path(m_shape);
            }
            else
            {
                m_ras.add_path(m_path);
            }

            agg_renderer_scanline.Default.render_scanlines_aa_solid(clippingProxy, m_ras, m_sl, new RGBA_Doubles(0.6, 0.9, 0.7, 0.8).GetAsRGBA_Bytes());

            gsv_text t = new gsv_text();
            t.SetFontSize(10.0);

            conv_stroke st = new conv_stroke(t);
            st.width(1.5);

            string buf;
            buf = string.Format("{0:F2} ms", tm);
            t.start_point(140.0, 30.0);
            t.text(buf);

            m_ras.add_path(st);
            agg_renderer_scanline.Default.render_scanlines_aa_solid(clippingProxy, m_ras, m_sl, new RGBA_Doubles(0, 0, 0).GetAsRGBA_Bytes());
            base.OnDraw(graphics2D);
        }

        static GuiHalSurface primaryWindow;

        public static void StartDemo()
        {
            GuiHalFactory.SetGuiBackend(GuiHalFactory.KnownGuiFactoriesIndexes.WindowsFormsBitmap);
#if SourceDepth24
            primaryWindow = GuiHalFactory.CreatePrimarySurface(440, 330, GuiHalSurface.CreateFlags.Resizable, GuiHalSurface.PixelFormat.PixelFormatBgr24);
#else
            primaryWindow = GuiHalFactory.CreatePrimarySurface(440, 330, GuiHalSurface.CreateFlags.Resizable, GuiHalSurface.PixelFormat.PixelFormatBgra32);
#endif

            primaryWindow.Caption = "AGG Example. Gaussian and Stack Blur";
            primaryWindow.AddChild(new blur_application());
            primaryWindow.Run();
        }

        [STAThread]
        public static void Main(string[] args)
        {
            StartDemo();
        }
    };
}